<?php

if (!defined('TS_ROOT')) die(basename(__FILE__));

function get_path_from_id ($id, $ext_id, $base_path, $first_div, $sec_div)
{
	global $ts_cfg;
	$ext = isset($ts_cfg['file_id_ext'][$ext_id]) ? $ts_cfg['file_id_ext'][$ext_id] : '';
	return ($base_path ? "$base_path/" : '') . floor($id/$first_div) .'/'. ($id % $sec_div) .'/'. $id . ($ext ? ".$ext" : '');
}

function get_avatar_path ($id, $ext_id, $base_path = null, $first_div = 10000, $sec_div = 100)
{
	global $ts_cfg;
	$base_path = isset($base_path) ? $base_path : $ts_cfg['avatars']['upload_path'];
	return get_path_from_id($id, $ext_id, $base_path, $first_div, $sec_div);
}

function delete_avatar ($user_id, $avatar_ext_id)
{
	$avatar_file = ($avatar_ext_id) ? get_avatar_path($user_id, $avatar_ext_id) : '';
	return ($avatar_file && file_exists($avatar_file)) ? @unlink($avatar_file) : false;
}

//
// Ajax
//
/**
* Encode PHP var to JSON (PHP -> JS)
**/

function ts_json_encode ($data)
{
	return json_encode($data);
}

function ts_json_decode ($data)
{
	if (!is_string($data)) trigger_error('invalid argument for '. __FUNCTION__, E_USER_ERROR);
	return json_decode($data, true);
}

function php2js ($a = false)
{
	if (is_null($a)) return 'null';
	if ($a === false) return 'false';
	if ($a === true) return 'true';
	if (is_scalar($a))
	{
		if (is_float($a))
		{
			$a = str_replace(",", ".", strval($a));
		}
		
		static $jsonReplaces = array(
			array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'),
			array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'),
		);
		return '"'. str_replace($jsonReplaces[0], $jsonReplaces[1], $a) .'"';
	}
	
	$isList = true;
	for ($i = 0, reset($a); $i < count($a); $i++, next($a))
	{
		if (key($a) !== $i)
		{
			$isList = false;
			break;
		}
	}
	$result = array();
	if ($isList)
	{
		foreach ($a as $v)
		{
			$result[] = php2js($v);
		}
		return '[ '. join(', ', $result) .' ]';
	}
	else
	{
		foreach ($a as $k => $v)
		{
			$result[] = php2js($k) .': '. php2js($v);
		}
		return '{ '. join(', ', $result) .' }';
	}
}

/**
 * Returns a size formatted in a more human-friendly format, rounded to the nearest GB, MB, KB..
 */
function humn_size ($size, $rounder = null, $min = null, $space = '&nbsp;')
{
	static $sizes   = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
	static $rounders = array(0,   0,    0,    2,    3,    3,    3,    3,    3);
	$size = (float) $size;
	$ext = $sizes[0];
	$rnd = $rounders[0];
	if ($min == 'KB' && $size < 1024)
	{
		$size = $size / 1024;
		$ext  = 'KB';
		$rounder = 1;
	}
	else
	{
		for ($i=1, $cnt=count($sizes); ($i < $cnt && $size >= 1024); $i++)
		{
			$size = $size / 1024;
			$ext  = $sizes[$i];
			$rnd  = $rounders[$i];
		}
	}
	if (!$rounder)
	{
		$rounder = $rnd;
	}
	return round($size, $rounder) . $space . $ext;
}

// $ids - array(id1,id2,..) or (string) id
function get_id_csv ($ids)
{
	$ids = array_values((array) $ids);
	array_deep($ids, 'intval', 'one-dimensional');
	return (string) join(',', $ids);
}
// $ids - array(id1,id2,..) or (string) id1,id2,..
function get_id_ary ($ids)
{
	$ids = is_string($ids) ? explode(',', $ids) : array_values((array) $ids);
	array_deep($ids, 'intval', 'one-dimensional');
	return (array) $ids;
}

//
// Auth
//
define('AUTH_LIST_ALL', 0);

// forum's ACL types (bb_forums: auth_view, auth_read... values)
define('AUTH_REG',   1);
define('AUTH_ACL',   2);
define('AUTH_ADMIN', 5);

// forum_perm bitfields - backward compatible with auth($type)
define('AUTH_ALL',        0);
define('AUTH_VIEW',       1);
define('AUTH_READ',       2);
define('AUTH_MOD',        3);
define('AUTH_POST',       4);
define('AUTH_REPLY',      5);
define('AUTH_EDIT',       6);
define('AUTH_DELETE',     7);
define('AUTH_STICKY',     8);
define('AUTH_ANNOUNCE',   9);
define('AUTH_VOTE',       10);
define('AUTH_POLLCREATE', 11);
define('AUTH_ATTACH',     12);

define('BF_AUTH_MOD', bit2dec(AUTH_MOD));

// When defining user permissions, take into account:
define('UG_PERM_BOTH',       1);  // both user and group
define('UG_PERM_USER_ONLY',  2);  // only personal user permissions
define('UG_PERM_GROUP_ONLY', 3);  // only group permissions

$bf['user_opt'] = array(
#   'dis_opt_name'       =>     ЗАПРЕТЫ используемые администраторами для пользователей
#   'user_opt_name'      =>     НАСТРОЙКИ используемые пользователями
	'user_viewemail'     => 0,  // Показывать e-mail
	'dis_avatar'         => 1,  // Запрет на аватар
	'user_viewonline'    => 2,  // Скрывать пребывание пользователя
	'user_hide_ads'      => 3,  // Запрет на показ рекламы
	'dis_post'           => 4,  // Запрет на отправку сообщений
	'dis_edit_profile'   => 5,  // Запретить редактировать профиль
);

function bit2dec ($bit_num)
{
	if (is_array($bit_num))
	{
		$dec = 0;
		foreach ($bit_num as $bit)
		{
			$dec |= (1 << $bit);
		}
		return $dec;
	}
	return (1 << $bit_num);
}

function bf_bit2dec ($bf_array_name, $key)
{
	global $bf;
	if (!isset($bf[$bf_array_name][$key]))
	{
		trigger_error(__FUNCTION__ .": bitfield '$key' not found", E_USER_ERROR);
	}
	return (1 << $bf[$bf_array_name][$key]);
}

function bf ($int, $bf_array_name, $key)
{
	return (bf_bit2dec($bf_array_name, $key) & (int) $int);
}

function setbit (&$int, $bit_num, $on)
{
	return ($on) ? $int |= (1 << $bit_num) : $int &= ~(1 << $bit_num);
}

class Date_Delta
{
	var $auto_granularity = array(
		60        => 'seconds',   // set granularity to "seconds" if delta less then 1 minute
		10800     => 'minutes',   // 3 hours
		259200    => 'hours',     // 3 days
		31363200  => 'mday',      // 12 months
		311040000 => 'mon',       // 10 years
	);
	var $intervals = array();
	var $format    = '';

	// Creates new object.
	function Date_Delta()
	{
		global $lang;

		$this->intervals = $lang['DELTA_TIME']['INTERVALS'];
		$this->format = $lang['DELTA_TIME']['FORMAT'];
	}

	// Makes the spellable phrase.
	function spellDelta($first, $last, $from = 'auto')
	{
		if ($last < $first)
		{
			$old_first = $first;
			$first = $last;
			$last = $old_first;
		}

		if ($from == 'auto')
		{
			$from = 'year';
			$diff = $last - $first;
			foreach ($this->auto_granularity as $seconds_count => $granule)
			{
				if ($diff < $seconds_count)
				{
					$from = $granule;
					break;
				}
			}
		}

		// Solve data delta.
		$delta = $this->getDelta($first, $last);
		if (!$delta) return false;

		// Make spellable phrase.
		$parts = array();
		$intervals = $GLOBALS['lang']['DELTA_TIME']['INTERVALS'];

		foreach (array_reverse($delta) as $k => $n)
		{
			if (!$n)
			{
				if ($k == $from)
				{
					if (!$parts)
					{
						$parts[] = declension($n, $this->intervals[$k], $this->format);
					}
					break;
				}
				continue;
			}
			$parts[] = declension($n, $this->intervals[$k], $this->format);
			if ($k == $from) break;
		}
		return join(' ', $parts);
	}

	// returns the associative array with date deltas.
	function getDelta($first, $last)
	{
		if ($last < $first) return false;

		// Solve H:M:S part.
		$hms = ($last - $first) % (3600 * 24);
		$delta['seconds'] = $hms % 60;
		$delta['minutes'] = floor($hms/60) % 60;
		$delta['hours']   = floor($hms/3600) % 60;

		// Now work only with date, delta time = 0.
		$last -= $hms;
		$f = getdate($first);
		$l = getdate($last); // the same daytime as $first!

		$dYear = $dMon = $dDay = 0;

		// Delta day. Is negative, month overlapping.
		$dDay += $l['mday'] - $f['mday'];
		if ($dDay < 0) {
			$monlen = $this->monthLength(date('Y', $first), date('m', $first));
			$dDay += $monlen;
			$dMon--;
		}
		$delta['mday'] = $dDay;

		// Delta month. If negative, year overlapping.
		$dMon += $l['mon'] - $f['mon'];
		if ($dMon < 0) {
			$dMon += 12;
			$dYear --;
		}
		$delta['mon'] = $dMon;

		// Delta year.
		$dYear += $l['year'] - $f['year'];
		$delta['year'] = $dYear;

		return $delta;
	}

	// Returns the length (in days) of the specified month.
	function monthLength($year, $mon)
	{
		$l = 28;
		while (checkdate($mon, $l+1, $year)) $l++;
		return $l;
	}
}

function delta_time ($timestamp_1, $timestamp_2 = TIMENOW, $granularity = 'auto')
{
	return $GLOBALS['DeltaTime']->spellDelta($timestamp_1, $timestamp_2, $granularity);
}

function get_select ($select, $selected = null, $return_as = 'html', $first_opt = '&raquo;&raquo; Выбрать ')
{
	$select_ary = array();

	switch ($select)
	{
		case 'groups':
			$sql = "SELECT group_id, group_name FROM ". BB_GROUPS ." WHERE group_single_user = 0 ORDER BY group_name";
			foreach (DB()->fetch_rowset($sql) as $row)
			{
				$select_ary[$row['group_name']] = $row['group_id'];
			}
			$select_name = 'g';
			break;

		case 'forum_tpl':
			$sql = "SELECT tpl_id, tpl_name FROM ". BB_TOPIC_TPL ." ORDER BY tpl_name";
			$select_ary[$first_opt] = 0;
			foreach (DB()->fetch_rowset($sql) as $row)
			{
				$select_ary[$row['tpl_name']] = $row['tpl_id'];
			}
			$select_name = 'forum_tpl_select';
			break;
	}

	return ($return_as == 'html') ? build_select($select_name, $select_ary, $selected) : $select_ary;
}

function clean_title ($str, $replace_underscore = false)
{
	$str = ($replace_underscore) ? str_replace('_', ' ', $str) : $str;
	$str = htmlCHR(str_compact($str));
	return $str;
}

class html_common
{
	var $options    = '';
	var $attr       = array();
	var $cur_attr   = null;
	var $max_length = HTML_SELECT_MAX_LENGTH;
	var $selected   = array();
	function build_select ($name, $params, $selected = null, $max_length = HTML_SELECT_MAX_LENGTH, $multiple_size = null, $js = '')
	{
		if (empty($params)) return '';
		$this->options = '';
		$this->selected = array_flip((array) $selected);
		$this->max_length = $max_length;
		$this->attr = array();
		$this->cur_attr =& $this->attr;
		if (isset($params['__attributes']))
		{
			$this->attr = $params['__attributes'];
			unset($params['__attributes']);
		}
		$this->_build_select_rec($params);
		$select_params  = ($js) ? " $js" : '';
		$select_params .= ($multiple_size) ? ' multiple="multiple" size="'. $multiple_size .'"' : '';
		$select_params .= ' name="'. htmlCHR($name) .'"';
		$select_params .= ' id="'. htmlCHR($name) .'"';
		return "\n<select class='form-control' $select_params>\n". $this->options ."</select>\n";
	}
	
	function _build_select_rec ($params)
	{
		foreach ($params as $opt_name => $opt_val)
		{
			$opt_name = rtrim($opt_name);
			if (is_array($opt_val))
			{
				$this->cur_attr =& $this->cur_attr[$opt_name];
				$label = htmlCHR(str_short($opt_name, $this->max_length));
				$this->options .= "\t<optgroup label=\"&nbsp;". $label ."\">\n";
				$this->_build_select_rec($opt_val);
				$this->options .= "\t</optgroup>\n";
				$this->cur_attr =& $this->attr;
			}
			else
			{
				$text  = htmlCHR(str_short($opt_name, $this->max_length));
				$value = ' value="'. htmlCHR($opt_val) .'"';
				$class = isset($this->cur_attr[$opt_name]['class']) ? ' class="'. $this->cur_attr[$opt_name]['class'] .'"' : '';
				$style = isset($this->cur_attr[$opt_name]['style']) ? ' style="'. $this->cur_attr[$opt_name]['style'] .'"' : '';
				$selected = isset($this->selected[$opt_val]) ? HTML_SELECTED : '';
				$disabled = isset($this->cur_attr[$opt_name]['disabled']) ? HTML_DISABLED : '';
				$this->options .= "\t\t<option". $class . $style . $selected . $disabled . $value .'>&nbsp;'. $text ."&nbsp;</option>\n";
			}
		}
	}
	
	function array2html ($array, $ul = 'ul', $li = 'li')
	{
		$this->out = '';
		$this->_array2html_rec($array, $ul, $li);
		return "<$ul class=\"tree-root\">{$this->out}</$ul>";
	}
	
	function _array2html_rec ($array, $ul, $li)
	{
		foreach ($array as $k => $v)
		{
			if (is_array($v))
			{
				$this->out .= "<$li><span class=\"b\">$k</span><$ul>";
				$this->_array2html_rec($v, $ul, $li);
				$this->out .= "</$ul></$li>";
			}
			else
			{
				$this->out .= "<$li><span>$v</span></$li>";
			}
		}
	}
	
	// all arguments should be already htmlspecialchar()d (if needed)
	function build_checkbox ($name, $title, $checked = false, $disabled = false, $class = null, $id = null, $value = 1)
	{
		$name     = ' name="'. $name .'" ';
		$value    = ' value="'. $value .'" ';
		$title    = ($class) ? '<span class="'. $class .'">'. $title .'</span>' : $title;
		$id       = ($id) ? " id=\"$id\" " : '';
		$checked  = ($checked) ? HTML_CHECKED : '';
		$disabled = ($disabled) ? HTML_DISABLED : '';
		return '<label><input type="checkbox" '. $id . $name . $value . $checked . $disabled .' />&nbsp;'. $title .'&nbsp;</label>';
	}
#	function build_option ($opt_name, $opt_val, $selected = null, $max_length = false)
#	{
#		return "\t\t<option value=\"". htmlCHR($opt_val) .'"'. (($selected) ? ' selected="selected"' : '') .'>'. htmlCHR(str_short($opt_name, $max_length)) ."</option>\n";
#	}
#	function build_optgroup ($label, $contents, $max_length = false)
#	{
#		return "\t<optgroup label=\"&nbsp;". htmlCHR(str_short($label, $max_length)) ."\">\n". $contents ."\t</optgroup>\n";
#	}
}
function build_select ($name, $params, $selected = null, $max_length = HTML_SELECT_MAX_LENGTH, $multiple_size = null, $js = '')
{
	global $html;
	
	return $html->build_select($name, $params, $selected, $max_length, $multiple_size, $js);
}

function build_checkbox ($name, $title, $checked = false, $disabled = false, $class = null, $id = null, $value = 1)
{
	global $html;
	
	return $html->build_checkbox($name, $title, $checked, $disabled, $class, $id, $value);
}

function replace_quote ($str, $double = true, $single = true)
{
	if ($double) $str = str_replace('"', '&quot;', $str);
	if ($single) $str = str_replace("'", '&#039;', $str);
	return $str;
}

/**
 * Choost russian word declension based on numeric [from dklab.ru]
 * Example for $expressions: array("ответ", "ответа", "ответов")
 */
function declension ($int, $expressions, $format = '%1$s %2$s')
{
	if (!is_array($expressions))
	{
		$expressions = $GLOBALS['lang']['DECLENSION'][strtoupper($expressions)];
	}
	if (count($expressions) < 3)
	{
		$expressions[2] = $expressions[1];
	}
	$count = intval($int) % 100;
	if ($count >= 5 && $count <= 20)
	{
		$result = $expressions['2'];
	}
	else
	{
		$count = $count % 10;
		if ($count == 1)
		{
			$result = $expressions['0'];
		}
		elseif ($count >= 2 && $count <= 4)
		{
			$result = $expressions['1'];
		}
		else
		{
			$result = $expressions['2'];
		}
	}
	return ($format) ? sprintf($format, $int, $result) : $result;
}

function obtain_word_list (&$orig_word, &$replacement_word)
{
	global $ts_cfg;
	
	if (!$ts_cfg['use_word_censor']) return;
	
	if (!$sql = CACHE('bb_cache')->get('censored'))
	{
		$sql = DB()->fetch_rowset("SELECT word, replacement FROM ". TS_WORDS);
		if(!$sql) $sql = array(array('word' => 1, 'replacement' => 1));
		CACHE('bb_cache')->set('censored', $sql, 7200);
	}
	
	foreach($sql as $row)
	{
		//$orig_word[] = '#(?<!\S)(' . str_replace('\*', '\S*?', preg_quote($row['word'], '#')) . ')(?!\S)#iu';
		$orig_word[] = '#(?<![\p{Nd}\p{L}_])(' . str_replace('\*', '[\p{Nd}\p{L}_]*?', preg_quote($row['word'], '#')) . ')(?![\p{Nd}\p{L}_])#iu';
		$replacement_word[] = $row['replacement'];
	}
	
	return true;
}

function ts_die ($msg_text)
{
	global $ajax, $ts_cfg, $lang, $template, $theme, $userdata;
	
	if (defined('IN_AJAX'))
	{
		$ajax->ajax_die($msg_text);
	}
	
	// Check
	if (defined('HAS_DIED'))
	{
		trigger_error(__FUNCTION__ .' was called multiple times', E_USER_ERROR);
	}
	
	define('HAS_DIED', 1);
	define('DISABLE_CACHING_OUTPUT', true);
	
	// If empty lang
	if (empty($lang))
	{
		require($ts_cfg['default_lang_dir'] .'main.php');
	}
	// If empty session
	if (empty($userdata))
	{
		$userdata = session_pagestart();
	}
	
	// If the header hasn't been output then do it
	if (!defined('PAGE_HEADER_SENT'))
	{
		if (empty($template))
		{
			$template = new Template(TS_ROOT ."templates/{$ts_cfg['tpl_name']}");
		}
		if (empty($theme))
		{
			$theme = setup_style();
		}
		require(PAGE_HEADER);
	}
	 
	// Check for lang variable
	if (!empty($lang[$msg_text]))
	{
		$msg_text = $lang[$msg_text];
	}

	$template->assign_vars(array(
		'TMPL_TS_DIE'   => true,
		'MESSAGE_TEXT'  => $msg_text,
	));
	
	$template->set_filenames(array('ts_die' => 'common.tmpl'));
	$template->pparse('ts_die');
	
	require(PAGE_FOOTER);
	
	exit;
}

function meta_refresh ($url, $time = 5)
{
	global $template;
	
	$template->assign_var('META', '<meta http-equiv="refresh" content="' . $time . ';url=' . $url . '" />');
}

function set_die_append_msg ($blog_id = null)
{
	global $lang, $template;
	
	$msg = '';
	$msg .= ($blog_id) ? '<p><a href="'. BLOG_URL . $blog_id .'">'. $lang['BLOG_RETURN'] .'</a></p>' : '';
	$msg .= '<p><a href="index.php">'. $lang['INDEX_RETURN'] .'</a></p>';
	$template->assign_var('TS_DIE_APPEND_MSG', $msg);
}

function set_pr_die_append_msg ($pr_uid)
{
	global $lang, $template;
	
	$template->assign_var('TS_DIE_APPEND_MSG', '
		<a href="'. PROFILE_URL . $pr_uid .'" onclick="return post2url(this.href, {after_edit: 1});">'. $lang['PROFILE_RETURN'] .'</a>
		<br /><br />
		<a href="user.php?mode=edit'. (IS_ADMIN ? "&amp;u=$pr_uid" : '') .'" onclick="return post2url(this.href, {after_edit: 1});">'. $lang['PROFILE_EDIT_RETURN'] .'</a>
		<br /><br />
		<a href="index.php">'. $lang['INDEX_RETURN'] .'</a>
	');
}

function ts_get_config ($table, $from_db = false, $update_cache = true)
{
	if ($from_db OR !$cfg = CACHE('ts_config')->get("config_{$table}"))
	{
		$cfg = array();
		foreach (DB()->fetch_rowset("SELECT * FROM $table") as $row)
		{
			$cfg[$row['config_name']] = $row['config_value'];
		}
		if ($update_cache)
		{
			CACHE('ts_config')->set("config_{$table}", $cfg);
		}
	}
	return $cfg;
}

function ts_update_config ($params, $table = TS_CONFIG)
{
	$updates = array();
	foreach ($params as $name => $val)
	{
		$updates[] = array(
			'config_name'  => $name,
			'config_value' => $val,
		);
	}
	$updates = DB()->build_array('MULTI_INSERT', $updates);
	
	DB()->query("REPLACE INTO $table $updates");
	
	// Update cache
	ts_get_config($table, true, true);
}

// Get Userdata, $u can be username or user_id. If $force_name is true, the username will be forced.
function get_userdata ($u, $force_name = false, $allow_guest = false)
{
	if (!$u) return false;
	
	if (intval($u) == GUEST_UID && $allow_guest)
	{
		if ($u_data = CACHE('ts_cache')->get('guest_userdata'))
		{
			return $u_data;
		}
	}
	
	$u_data = array();
	$name_search = false;
	$exclude_anon_sql = (!$allow_guest) ? "AND user_id != ". GUEST_UID : '';
	
	if ($force_name || !is_numeric($u))
	{
		$name_search = true;
		$where_sql = "WHERE username = '". DB()->escape(clean_username($u)) ."'";
	}
	else
	{
		$where_sql = "WHERE user_id = ". (int) $u;
	}
	
	$sql = "SELECT * FROM ". TS_USERS ." $where_sql $exclude_anon_sql LIMIT 1";
	
	if (!$u_data = DB()->fetch_row($sql))
	{
		if (!is_int($u) && !$name_search)
		{
			$where_sql = "WHERE username = '". DB()->escape(clean_username($u)) ."'";
			$sql = "SELECT * FROM ". TS_USERS ." $where_sql $exclude_anon_sql LIMIT 1";
			$u_data = DB()->fetch_row($sql);
		}
	}
	if ($u_data['user_id'] == GUEST_UID)
	{
		CACHE('ts_cache')->set('guest_userdata', $u_data);
	}
	
	return $u_data;
}

function clean_username ($username)
{
	$username = mb_substr(htmlspecialchars(str_replace("\'", "'", trim($username))), 0, 25, 'UTF-8');
	$username = ts_rtrim($username, "\\");
	$username = str_replace("'", "\'", $username);
	return $username;
}

function ts_ltrim ($str, $charlist = false)
{
	if ($charlist === false)
	{
		return ltrim($str);
	}
	$str = ltrim($str, $charlist);
	
	return $str;
}

function ts_rtrim ($str, $charlist = false)
{
	if ($charlist === false)
	{
		return rtrim($str);
	}
	$str = rtrim($str, $charlist);
	
	return $str;
}

function caching_output ($enabled, $mode, $cache_var_name, $ttl = 300)
{
	if (!$enabled || !CACHE('ts_cache')->used)
	{
		return;
	}
	if ($mode == 'send')
	{
		if ($cached_contents = CACHE('ts_cache')->get($cache_var_name))
		{
			bb_exit($cached_contents);
		}
	}
	else if ($mode == 'store')
	{
		if ($output = ob_get_contents())
		{
			CACHE('ts_cache')->set($cache_var_name, $output, $ttl);
		}
	}
}

function ts_realpath ($path)
{
	return (!@function_exists('realpath') || !@realpath(INC_DIR . 'functions.php')) ? $path : @realpath($path);
}

function print_page($args, $type = '', $mode = '')
{
	global $template, $gen_simple_header;
	
	$tpl = (is_array($args) && !empty($args['tmpl'])) ? $args['tmpl'] : $args;
	$tpl = ($type === 'admin') ? ADMIN_TPL_DIR . $tpl : $tpl;
	
	$gen_simple_header = (is_array($args) && !empty($args['simple']) OR $type === 'simple') ? true : $gen_simple_header;
	
	if ($mode !== 'no_header')
	{
		require(PAGE_HEADER);
	}
	
	$template->set_filenames(array('body' => $tpl));
	$template->pparse('body');
	
	if ($mode !== 'no_footer')
	{
		require(PAGE_FOOTER);
	}

}

function setup_style ()
{
	global $ts_cfg, $template, $userdata;
	
	// AdminCP works only with default template
	$tpl_dir_name = defined('IN_ADMIN') ? 'default'   : basename($ts_cfg['tpl_name']);
	$stylesheet   = defined('IN_ADMIN') ? 'bootstrap.css'  : basename($ts_cfg['stylesheet']);
	
	if (!IS_GUEST && !empty($userdata['tpl_name']))
	{
		foreach ($ts_cfg['templates'] as $folder => $name)
		{
			if ($userdata['tpl_name'] == $folder) $tpl_dir_name = basename($userdata['tpl_name']);
		}
	}
	
	$template = new Template(TEMPLATES_DIR . $tpl_dir_name);
	$css_dir = 'styles/bootstrap/css/';
	
	$template->assign_vars(array(
		'TS_ROOT'          => TS_ROOT,
		'SPACER'           => make_url('styles/images/spacer.gif'),
		'STYLESHEET'       => make_url($css_dir . $stylesheet),
		'EXT_LINK_NEW_WIN' => $ts_cfg['ext_link_new_win'],
		'TPL_DIR'          => make_url($css_dir),
		'SITE_URL'         => make_url('/'),
	));
	
	require(TEMPLATES_DIR . $tpl_dir_name .'/tpl_config.php');
	
	$theme = array('template_name' => $tpl_dir_name);
	
	return $theme;
}

function login_redirect ($url = '')
{
	redirect(SIGN_IN . '?redirect='. (($url) ? $url : (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/')));
}

function redirect ($url)
{
	global $ts_cfg;
	
	if (headers_sent($filename, $linenum))
	{
		trigger_error("Headers already sent in $filename($linenum)", E_USER_ERROR);
	}
	
	if (strstr(urldecode($url), "\n") || strstr(urldecode($url), "\r") || strstr(urldecode($url), ';url'))
	{
		ts_die('Tried to redirect to potentially insecure url');
	}
	
	$url = trim($url);
	
	$server_protocol = ($ts_cfg['cookie_secure']) ? 'https://' : 'http://';
	$server_name = preg_replace('#^\/?(.*?)\/?$#', '\1', trim($ts_cfg['server_name']));
	$server_port = ($ts_cfg['server_port'] <> 80) ? ':' . trim($ts_cfg['server_port']) : '';
	$script_name = preg_replace('#^\/?(.*?)\/?$#', '\1', trim($ts_cfg['script_path']));
	
	if ($script_name)
	{
		$script_name = "/$script_name";
		$url = preg_replace("#^$script_name#", '', $url);
	}
	
	$redirect_url = $server_protocol . $server_name . $server_port . $script_name . preg_replace('#^\/?(.*?)\/?$#', '/\1', $url);
	// Behave as per HTTP/1.1 spec for others
	
	header('Location: '. $redirect_url);
	
	exit;
}

//
// Poll
//
function get_poll_data_items_js ($blog_id)
{
	if (!$blog_id_csv = get_id_csv($blog_id))
	{
		return is_array($blog_id) ? array() : false;
	}
	$items = array();
	
	if (!$poll_data = CACHE('ts_poll_data')->get("poll_$blog_id"))
	{
		$poll_data = DB()->fetch_rowset("
			SELECT blog_id, vote_id, vote_text, vote_result
			FROM ". TS_POLL_VOTES ."
			WHERE blog_id IN($blog_id_csv)
			ORDER BY blog_id, vote_id
		");
		CACHE('ts_poll_data')->set("poll_$blog_id", $poll_data);
	}
	
	foreach ($poll_data as $row)
	{
		$opt_text_for_js   = htmlCHR($row['vote_text']);
		$opt_result_for_js = (int) $row['vote_result'];
		$items[$row['blog_id']][$row['vote_id']] = array($opt_text_for_js, $opt_result_for_js);
	}
	
	foreach ($items as $k => $v)
	{
		$items[$k] = ts_json_encode($v);
	}
	
	return is_array($blog_id) ? $items : $items[$blog_id];
}

function poll_is_active ($t_data)
{
	global $ts_cfg;
	return ($t_data['blog_vote'] == 1 && $t_data['blog_time'] > TIMENOW - $ts_cfg['poll_max_days'] * 86400);
}

// Create date / time with format and friendly date
function bb_date ($gmepoch, $format = false, $friendly_date = true)
{
	global $ts_cfg, $lang, $userdata;

	if (!$format) $format = $ts_cfg['default_dateformat'];
	if (empty($lang)) require_once($ts_cfg['default_lang_dir'] .'main.php');

	if (empty($userdata['session_logged_in']))
	{
		$tz = $ts_cfg['board_timezone'];
	}
	else
	{
		$tz = $userdata['user_timezone'];
	}

	$date = gmdate($format, $gmepoch + (3600 * $tz));

	if ($friendly_date)
	{
		$time_format = ' H:i';

		$today = gmdate('d', TIMENOW + (3600 * $tz));
		$month = gmdate('m', TIMENOW + (3600 * $tz));
		$year  = gmdate('Y', TIMENOW + (3600 * $tz));

		$date_today = gmdate('d', $gmepoch + (3600 * $tz));
		$date_month = gmdate('m', $gmepoch + (3600 * $tz));
		$date_year  = gmdate('Y', $gmepoch + (3600 * $tz));

		if ($date_today == $today && $date_month == $month && $date_year == $year)
		{
			$date = 'today' . gmdate($time_format, $gmepoch + (3600 * $tz));
		}
		elseif ($today != 1 && $date_today == ($today-1) && $date_month == $month && $date_year == $year)
		{
			$date = 'yesterday' . gmdate($time_format, $gmepoch + (3600 * $tz));
		}
		elseif ($today == 1 && $month != 1)
		{
			$yesterday = date ('t', mktime(0, 0, 0, ($month-1), 1, $year));
			if ($date_today == $yesterday && $date_month == ($month-1) && $date_year == $year)
				$date = 'yesterday' . gmdate($time_format, $gmepoch + (3600 * $tz));
		}
		elseif ($today == 1 && $month == 1)
		{
			$yesterday = date ('t', mktime(0, 0, 0, 12, 1, ($year -1)));
			if ($date_today == $yesterday && $date_month == 12 && $date_year == ($year-1))
				$date = 'yesterday' . gmdate($time_format, $gmepoch + (3600 * $tz));
		}
	}

	return ($ts_cfg['translate_dates']) ? strtr(strtoupper($date), $lang['DATETIME']) : $date;
}

function birthday_age ($date)
{
	global $ts_cfg;
	if (!$date) return;

	$tz = TIMENOW + (3600 * $ts_cfg['board_timezone']);
	return delta_time(strtotime($date, $tz));
}

//
// Pagination routine, generates
// page number sequence
//
function generate_pagination ($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = TRUE)
{
	global $lang, $template;
	
// Pagination Mod
	$begin_end = 3;
	$from_middle = 1;
	
/*
	By default, $begin_end is 3, and $from_middle is 1, so on page 6 in a 12 page view, it will look like this:
	a, d = $begin_end = 3
	b, c = $from_middle = 1
 "begin"        "middle"           "end"
    |              |                 |
    |     a     b  |  c     d        |
    |     |     |  |  |     |        |
    v     v     v  v  v     v        v
    1, 2, 3 ... 5, 6, 7 ... 10, 11, 12
	Change $begin_end and $from_middle to suit your needs appropriately
*/

	$total_pages = ceil($num_items/$per_page);
	
	if ($total_pages == 1 || $num_items == 0)
	{
		return '';
	}
	
	$on_page = floor($start_item / $per_page) + 1;
	$page_string = '';
	
	if ($total_pages > ((2*($begin_end + $from_middle)) + 2))
	{
		$init_page_max = ( $total_pages > $begin_end ) ? $begin_end : $total_pages;
		
		for ($i = 1; $i < $init_page_max + 1; $i++)
		{
			$page_string .= ( $i == $on_page ) ? '<b>' . $i . '</b>' : '<a href="' . $base_url . "&amp;start=" . ( ( $i - 1 ) * $per_page ) . '">' . $i . '</a>';
			if ($i <  $init_page_max)
			{
				$page_string .= ", ";
			}
		}
		if ($total_pages > $begin_end)
		{
			if ($on_page > 1  && $on_page < $total_pages)
			{
				$page_string .= ( $on_page > ($begin_end + $from_middle + 1) ) ? ' ... ' : ', ';
				$init_page_min = ( $on_page > ($begin_end + $from_middle) ) ? $on_page : ($begin_end + $from_middle + 1);
				$init_page_max = ( $on_page < $total_pages - ($begin_end + $from_middle) ) ? $on_page : $total_pages - ($begin_end + $from_middle);
				for ($i = $init_page_min - $from_middle; $i < $init_page_max + ($from_middle + 1); $i++)
				{
					$page_string .= ($i == $on_page) ? '<b>' . $i . '</b>' : '<a href="' . $base_url . "&amp;start=" . ( ( $i - 1 ) * $per_page ) . '">' . $i . '</a>';
					if ($i <  $init_page_max + $from_middle)
					{
						$page_string .= ', ';
					}
				}
				$page_string .= ( $on_page < $total_pages - ($begin_end + $from_middle) ) ? ' ... ' : ', ';
			}
			else
			{
				$page_string .= '&nbsp;...&nbsp;';
			}
			for ($i = $total_pages - ($begin_end - 1); $i < $total_pages + 1; $i++)
			{
				$page_string .= ( $i == $on_page ) ? '<b>' . $i . '</b>'  : '<a href="' . $base_url . "&amp;start=" . ( ( $i - 1 ) * $per_page ) . '">' . $i . '</a>';
				if ($i <  $total_pages)
				{
					$page_string .= ", ";
				}
			}
		}
	}
	else
	{
		for ($i = 1; $i < $total_pages + 1; $i++)
		{
			$page_string .= ( $i == $on_page ) ? '<b>' . $i . '</b>' : '<a href="' . $base_url . "&amp;start=" . ( ( $i - 1 ) * $per_page ) . '">' . $i . '</a>';
			if ($i <  $total_pages)
			{
				$page_string .= ', ';
			}
		}
	}
	if ($add_prevnext_text)
	{
		if ($on_page > 1)
		{
			$page_string = ' <a href="' . $base_url . "&amp;start=" . ( ( $on_page - 2 ) * $per_page ) . '">' . $lang['PREVIOUS_PAGE'] . '</a>&nbsp;&nbsp;' . $page_string;
		}
		if ($on_page < $total_pages)
		{
			$page_string .= '&nbsp;&nbsp;<a href="' . $base_url . "&amp;start=" . ( $on_page * $per_page ) . '">' . $lang['NEXT_PAGE'] . '</a>';
		}
	}
	
	$pagination = ($page_string) ? '<a class="menu-root" href="#pg-jump">'. $lang['GOTO_PAGE'] .'</a> :&nbsp;&nbsp;'. $page_string : '';
	$pagination = str_replace('&amp;start=0', '', $pagination);
	
	$template->assign_vars(array(
		'PAGINATION'   => $pagination,
		'PAGE_NUMBER'  => sprintf($lang['PAGE_OF'], ( floor($start_item/$per_page) + 1 ), ceil( $num_items / $per_page )),
		'PG_BASE_URL'  => $base_url,
		'PG_PER_PAGE'  => $per_page,
	));
	
	return $pagination;
}

// http://forum.dklab.ru/php/advises/UrlreplaceargChangesValueOfParameterInUrl.html
function url_arg ($url, $arg, $value, $amp = '&amp;')
{
	$arg = preg_quote($arg, '/');
	
	// разделяем URL и ANCHOR
	$anchor = '';
	if (preg_match('/(.*)(#.*)/s', $url, $m))
	{
		$url    = $m[1];
		$anchor = $m[2];
	}
	
	// заменяем параметр, если он существует
	if (preg_match("/((\?|&|&amp;)$arg=)[^&]*/s", $url, $m))
	{
		$cur = $m[0];
		$new = is_null($value) ? '' : $m[1] . urlencode($value);
		$url = str_replace($cur, $new, $url);
	}
	
	// добавляем параметр
	else if (!is_null($value))
	{
		$div = (strpos($url, '?') !== false) ? $amp : '?';
		$url = $url . $div . $arg .'='. urlencode($value);
	}
	
	return $url . $anchor;
}

/**
* set_var
*
* Set variable, used by {@link request_var the request_var function}
*
* @access private
*/
function set_var (&$result, $var, $type, $multibyte = false, $strip = true)
{
	settype($var, $type);
	$result = $var;
	if ($type == 'string')
	{
		$result = trim(htmlspecialchars(str_replace(array("\r\n", "\r"), array("\n", "\n"), $result)));
		if (!empty($result))
		{
			// Make sure multibyte characters are wellformed
			if ($multibyte)
			{
				if (!preg_match('/^./u', $result))
				{
					$result = '';
				}
			}
		}
		$result = ($strip) ? stripslashes($result) : $result;
	}
}
/**
* request_var
*
* Used to get passed variable
*/
function request_var ($var_name, $default, $multibyte = false, $cookie = false)
{
	if (!$cookie && isset($_COOKIE[$var_name]))
	{
		if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
		{
			return (is_array($default)) ? array() : $default;
		}
		$_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
	}
	if (!isset($_REQUEST[$var_name]) || (is_array($_REQUEST[$var_name]) && !is_array($default)) || (is_array($default) && !is_array($_REQUEST[$var_name])))
	{
		return (is_array($default)) ? array() : $default;
	}
	$var = $_REQUEST[$var_name];
	if (!is_array($default))
	{
		$type = gettype($default);
	}
	else
	{
		list($key_type, $type) = each($default);
		$type = gettype($type);
		$key_type = gettype($key_type);
		if ($type == 'array')
		{
			reset($default);
			$default = current($default);
			list($sub_key_type, $sub_type) = each($default);
			$sub_type = gettype($sub_type);
			$sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
			$sub_key_type = gettype($sub_key_type);
		}
	}
	if (is_array($var))
	{
		$_var = $var;
		$var = array();
		foreach ($_var as $k => $v)
		{
			set_var($k, $k, $key_type);
			if ($type == 'array' && is_array($v))
			{
				foreach ($v as $_k => $_v)
				{
					if (is_array($_v))
					{
						$_v = null;
					}
					set_var($_k, $_k, $sub_key_type);
					set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
				}
			}
			else
			{
				if ($type == 'array' || is_array($v))
				{
					$v = null;
				}
				set_var($var[$k], $v, $type, $multibyte);
			}
		}
	}
	else
	{
		set_var($var, $var, $type, $multibyte);
	}
	return $var;
}

function str_short ($text, $max_length, $space = ' ')
{
	if ($max_length && mb_strlen($text, 'UTF-8') > $max_length)
	{
		$text = mb_substr($text, 0, $max_length, 'UTF-8');
		if ($last_space_pos = $max_length - intval(strpos(strrev($text), $space)))
		{
			if ($last_space_pos > round($max_length * 3/4))
			{
				$last_space_pos--;
				$text = mb_substr($text, 0, $last_space_pos, 'UTF-8');
			}
		}
		$text .= '...';
		$text = preg_replace('!&#?(\w+)?;?(\w{1,5})?\.\.\.$!', '...', $text);
	}
	return $text;
}

function wbr ($text, $max_word_length = HTML_WBR_LENGTH)
{
	return preg_replace("/([\w\->;:.,~!?(){}@#$%^*\/\\\\]{". $max_word_length ."})/ui", '$1<wbr>', $text);
}

function bb_captcha ($mode, $callback = '')
{
	global $ts_cfg, $userdata;
	
	require_once(CLASS_DIR .'recaptcha.php');
	
	$secret = $ts_cfg['captcha']['secret_key'];
	$public = $ts_cfg['captcha']['public_key'];
	$theme  = $ts_cfg['captcha']['theme'];
	$lang   = $ts_cfg['lang'][$userdata['user_lang']]['captcha'];
	
	$reCaptcha = new ReCaptcha($secret);
	
	switch ($mode)
	{
		case 'get':
			return "
				<script type=\"text/javascript\">
					var onloadCallback = function() {
						grecaptcha.render('tp-captcha', {
							'sitekey'  : '" . $public . "',
							'theme'    : '" . $theme . "',
							'callback' : '" . $callback . "'
						});
					};
				</script>
				<div id=\"tp-captcha\"></div>
				<script src=\"https://www.google.com/recaptcha/api.js?onload=onloadCallback&hl=" . $lang . "&render=explicit\" async defer></script>";
			break;
	
		case 'check':
			$resp = null;
			$error = null;
			$g_resp = request_var('g-recaptcha-response', '');
		
			if ($g_resp) {
				$resp = $reCaptcha->verifyResponse($_SERVER["REMOTE_ADDR"], $g_resp);
			}
			if ($resp != null && $resp->success) {
				return true;
			} else {
				return false;
			}
			break;

		default:
			bb_simple_die(__FUNCTION__ .": invalid mode '$mode'");
	}
	return false;
}

function profile_url ($data)
{
	global $ts_cfg, $lang, $datastore;
	
	if (!$ranks = $datastore->get('ranks'))
	{
		$datastore->update('ranks');
		$ranks = $datastore->get('ranks');
	}
	
	$user_rank = !empty($data['user_rank']) ? $data['user_rank'] : 0;
	if (isset($ranks[$user_rank]))
	{
		$title = $ranks[$user_rank]['rank_title'];
		$style = $ranks[$user_rank]['rank_style'];
	}
	
	if (empty($title)) $title = $lang['USER'];
	if (empty($style)) $style = 'label label-default';
	
	if (!$ts_cfg['color_nick']) $style = '';
	
	$username = !empty($data['username']) ? $data['username'] : $lang['GUEST'];
	$user_id = (!empty($data['user_id']) && $username != $lang['GUEST']) ? $data['user_id'] : GUEST_UID;
	
	$profile = '<span title="'. $title .'" class="'. $style .'">'. $username .'</span>';
	
	if (!in_array($user_id, array('', GUEST_UID, BOT_UID)) && $username)
	{
		$profile = '<a href="'. make_url(PROFILE_URL . $user_id) .'">'. $profile .'</a>';
	}
	return $profile;
}

function get_avatar ($user_id, $ext_id, $allow_avatar = true, $size = true, $height = '', $width = '')
{
	global $ts_cfg;
	
	if ($size)
	{
		// TODO размеры: s, m, l + кеширование
	}
	
	$height = ($height != '') ? 'height="'. $height .'"' : '';
	$width  = ($width != '') ? 'width="'. $width .'"' : '';
	
	$user_avatar = '<img class="img-thumbnail" src="'. make_url($ts_cfg['avatars']['upload_path'] . $ts_cfg['avatars']['no_avatar']) .'" alt="'. $user_id .'" '. $height .' '. $width .' />';
	
	if ($user_id == BOT_UID && $ts_cfg['avatars']['bot_avatar'])
	{
		$user_avatar = '<img class="img-thumbnail" src="'. make_url($ts_cfg['avatars']['upload_path'] . $ts_cfg['avatars']['bot_avatar']) .'" alt="'. $user_id .'" '. $height .' '. $width .' />';
	}
	else if ($allow_avatar && $ext_id)
	{
		if (file_exists(get_avatar_path($user_id, $ext_id)))
		{
			$user_avatar = '<img class="img-thumbnail" src="'. make_url(get_avatar_path($user_id, $ext_id)) .'" alt="'. $user_id .'" '. $height .' '. $width .' />';
		}
	}
	return $user_avatar;
}